#!usr/bin/python
#-*- coding:utf-8 -*-
# 参数除了 DEFAULT_ROUTE 需要设置为自己的外, 其他默认的就很好了.

# 监听地址, 终止信号
SERVER_ADDRESS= "127.0.0.1:8088"
SHUTDOWN_NOW= False

#Test= GAEServer("http://127.0.0.1:8080/fetch2.py")

# 默认路由 以及 重试次数  
DEFAULT_ROUTE= GAEServer("http://lyricconch-test.appspot.com/fetch2.py",(
    0x6904401923F92CB14D9479C9CC4B2E2644964998102B8E6BCAEF2FD6EBBF31A3,
    0,0x1A1CF75A30171BA6969DA6B70C330E19F8A40BD) ) 
RETRIES_ROUTE= 1 

# 如果本地连接获取失败, 那么将尝试使用默认路由
LocalHost.FAIL_SAFE= DEFAULT_ROUTE

# 这两个是主程序和配置文件互相交流的接口
# 高级用户通过自定义其实现可使控制所有的请求响应行为 
def FIND_ROUTE(remote, method, path, headers):
    """ 在gfwlist中的走GAE代理, 在abplist中的就地返回404, 其他直接连接. """
        
    if ABPed.match(path): 
        return 404, "NOT FOUND"
        
    if GFWed.match(path): return {
        'url':"http://lyricconch-test.appspot.com/fetch2.py",
        "key":(0x6904401923F92CB14D9479C9CC4B2E2644964998102B8E6BCAEF2FD6EBBF31A3,
               0,0x1A1CF75A30171BA6969DA6B70C330E19F8A40BD),
    }                
          
def BIND_ROUTE(path, new, old, final, time):
    """ 直接连接时前32个字符相同的地址两次重试完成的间隔在30秒, 加入 gfwlist"""        
    
    if final and isinstance(old, LocalHost):
        if time-Before.get(path[:32], 0)<=30: 
            return GFWed.update(path)
        Before[path[:32]]= time

# 在 with Static 缩进内的区域是静态的, 仅会被执行一次
# 在配置文件因变更而重载时候是会被忽略的, 等价于 If FirstRun:
# **差别在于 with Static 能有更好的控制, If 做不到.
with Static("Scope for ABP"):
    # 输出一条信息级别的日志. %s 就在引用后续的 1+2, 它将显示出 "... from 3 sources..." 
    Logger.info("Create Matcher ABP from %s sources", 1+2)
    # 初始化一个叫 ABPed 的比较器, 显示的名字叫 ABP, 存储文件是 adblock.list
    ABPed= Matcher("ABP", "adblock.list")
    # 用本地连接(LocalHost)获取(urlfetch) easylist 并以此初始化(init) ABPed
    #ABPed.init(LocalHost().urlfetch(path=
    #    "http://easylist-downloads.adblockplus.org/easylist.txt")[-1])
    Logger.info("Easylist has been loaded to Matcher %s", ABPed)
    # 尝试(try)本地连接获取 googlecode 上的 chinalist(googlecode.com很容易被和谐的)
    # 失败的话(except)就当啥都没获取(data=None)
    try: data= LocalHost().urlfetch(path=
        "http://adblock-chinalist.googlecode.com/svn/trunk/adblock.txt")[-1]
    except: data=None 
    # 如果有数据的话(data)那么就直接用来初始化 ABPed
    # 否则的话(or)用默认路由所获取的数据来初始化
    # 失败了(except)就当啥都没发生过(pass)
    try:
        ABPed.init(data or DEFAULT_ROUTE.urlfetch(path=
        "http://adblock-chinalist.googlecode.com/svn/trunk/adblock.txt")[-1])
        Logger.info("Chinalist has been loaded to Matcher %s", ABPed)
    except: pass
    # 打印这个比较器, 看看我们有多少条规则, 以及是否有 "坏" 规则.
    print ABPed.state()     

# 这个区间同样只能被执行一次.
with Static("Scope for GFW"):
    Before= {}
    # 作业: 在 googlecode.com 被封的情况下, 如何拿到并使用 autoproxy-gfwlist?
    # 这是从初级使用到自定义配置的桥梁, 请尽量自己解决.
    Logger.info("Create Matcher GFW, use autoproxy.list and gfwlist")
    GFWed= Matcher("GFW","autoproxy.list")
    #GFWed.init(LocalHost().urlfetch(path=
    #    "http://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt")[-1])
    print GFWed.state()  
     
         
""" 请注意: 
以 # 开头的是注释, 将被忽略, 其他的非空白行都是有效内容, 格式如 python 语法
请在原有基础上修改, 并且不要使用 TAB 字符. 

这些名字为配置选项(带*的选项需要放在前面, 并且重新运行程序才能使修改生效):

SHUTDOWN_NOW: 程序运行控制, 无窗口又不知道该杀哪个 pythonw.exe 时使用
    为False 忽略, 为True 时程序终止, 为None 时程序重启.
    
(*)SERVER_ADDRESS: 程序监听的地址, 字符串或元组.

(*)FORBIDDEN_*: 用户控制, 禁止某些功能(预留, 暂时未实现) 
    设置为 True 则对应功能被禁止, False 则默认允许
    如果某功能不被系统支持, 则对应项无效(比如说传输加密需要pyCrypto)
    FORBIDDEN_HTTPS 禁止 HTTPS 连接 通过 GAE
    FORBIDDEN_CERTS 禁止 证书欺骗 (警告: 设为False将有严重安全风险)
    FORBIDDEN_CRYPT 禁止 GAE 传输加密
    FORBIDDEN_PERSIST 禁止 HTTP/1.1 持久连接
    FORBIDDEN_PIPELINE 禁止 HTTP/1.1 流水线请求

(*)LOGGING_*: 日志相关, 程序的信息输出
    测试者应将级别设置为DEBUG并输出到文件, 一般使用者可设为WARN或None 
    LOGGING_LEVEL: 日志等级, 字符串 
        为 DEBUG INFO WARN ERROR CRITIACL 之一, 要带引号
        从低到高依次为: 调试,信息,警告,错误,致命.
    LOGGING_OUTPUT: 日志输出, 字符串 或 None(无日志)
        若字符串不为空, 则发送到指定文件
        若字符串内不包含下划线(_), 则还在标准错误上输出        
    **LOGGING_FORMAT: 日志格式, 字符串
        指定日志输出的格式: 可选的格式符参 python 目录下的 logging.py

DEFAULT_ROUTE: 默认的路由. 
    必须是 GAEServer,LocalHost,DirectProxy,EndPoint 其一的实例.
    这个不允许返回Adapter类型或后续所述的那些快捷返回.

RETRIES_ROUTE: 路由失败的重试次数
    这是一个整数, 0为不重试, 大于0 则为重试上限
    
FIND_ROUTE: 程序的请求, 让用户决定某个请求该经过那种路由
    这个配合 Matcher 和 EndPoint 是可以玩出很多花样滴 ... 
    注意 HTTP/1.1 pipeline 请求使用了 sockio 加速传输, 是以并不经过这个阶段.
    可以就地解决的连接使用 EndPoint 路由(比如广告),
    被墙的连接交给 GAEServer 使用 GAE 代理,
    一般的连接用 LocalHost 直接连接,
    或者需要代理才能连接, 用 DirectProxy.
    @return None / str / tuple / dict / list / Adapter实例 / Adapter类型
    @param [str] remote 客户端地址, 如"127.0.0.1", "192.168.1.24"
    @param [str] method HTTP 请求方法
    @param [str] path HTTP 请求路径
    @param [dict] headers HTTP请求头, key 都是小写的. 
    
BIND_ROUTE: 程序的反馈, 认为某地址应该关联到特定的路由
    发生在旧路由处理某地址的请求失败, 程序 将开始重试 或 已重试成功 的时刻,
    由此反馈给用户告知该地址 可能 或 确定 可以关联到新路由, 同时避免下次重试.
    @return 真/假 分别为 取消/允许(final=False)重试 或 已响应/忽略(final=True)路由关联
    @param [str] path 请求的目标地址
    @param [Adapter] new 将要(final=False) 或 完成请求(final=True) 的路由
    @param [Adapter] old 处理失败(final=False) 或 原始(final=True) 的路由
    @param [bool] final 为 True 表示请求已经完成, 否则表示将开始尝试.
    
     
这些名字为辅助名称, 为 FIND_ROUTE/BIND_ROUTE 服务(带 * 的位置可以放空):

DirectProxy: 表示一个直接代理:
    语法: DirectProxy(代理服务器地址, 期望的延迟*, 别名*)
    快捷: 返回字符串与此等效(仅address), 如: "www.google.cn:80"    
    原型: class DirectProxy(address, gate=50, alias="") 
            extends Adapter implements Reusable thread StaticDaemon(connpred)
    @param [str/tuple] address 代理服务器地址
    @param [int] gate 期望的延迟, 负值(不作测试)与零总使用预连接池, 正值则延迟高于此值才使用(毫秒) 
    @param [str] alias 别名, 可作为ID, 如果设置了日志输出也会使用该值
    隐藏参数:
        DirectProxy.POOL_LIMIT: 三个整数(tuple), 为连接池下限, 上限, 初值
        **DirectProxy.POOL_FBACK: 一对正数(tuple), 为锁相反馈参数 FINC, FDEC
            FINC 决定连接池的再生速率, FDEC 决定连接池的衰退速率.
            当创建 DirectProxy 时, 发现连接延迟高于期望值, 则使用预连接池.
            这个手段用于降低真正连接时的延迟, 但是需要额外的连接(以及超时控制).
        DirectProxy.PING_BASE: 整数, 基础的延时, 默认将用连接自身的延时来设置.    
        DirectProxy.FETCH_TIMEOUT: 数值, 使用 urlfetch 时默认的超时时间

LocalHost: 表示本地主机:
    语法: LocalHost() 产生一个本地主机 (这是一个单例)
    快捷: 返回空值None/空返回值/无返回值 与此等效.
    原型: LocalHost() extends DirectProxy implements Singleton thread StaticDaemon(dnscached)
    隐藏参数:
        LocalHost.DNS_TIMEOUT: 整数 
            若小于零, 则不使用 DNS 缓存
            若等于零, 则 DNS 缓存永不过期
            若大于零, 则为 DNS 缓存过期时间, 以分钟为单位    
        LocalHost.PSOCK_GATE: 整数
        LocalHost.PSOCK_CONN: 整数
            在一个周期内(6s)内如果同一个地址出现 PSOCK_GATE 次连接请求,
            那么程序将预建立 PSOCK_CONN 个额外连接以供后续直接使用.

GAEServer: 表示一个 GAE 的服务端:
    语法: GAEServer(服务端地址, 加密所用的RSA私钥*, 连向Appengine的代理*, 别名*)
    快捷: 返回一个字典与此等效, 如: {"url"=..., "key"=...}  
    原型: GAEServer(url, key=None, wrap=None, alias=None)  
            extends DirectProxy implements Reusable thread StaticDaemon(gaeidled)
    @param [str] url GAE 服务端地址, 不能省略 http:// 或 https://
    @param [tuple] key 加密用的RSAKey, 为(N,E,D,...), N,E,D为大整数, E,D必须有一项不为零 
    @param [str] wrap 本地主机连接 GAE 服务端时使用的代理
    @param [str] alias 别名, 可作为ID, 如果设置了日志输出也会使用该值
    隐藏参数:
        GAEServer.FETCH_PIPELINE: 整数, 一次 GAE 请求可包含最大本地请求数.
        GAEServer.CONTENT_LENGTH: 整数, 一次 GAE 请求可携带的最大字节数.
        GAEServer.IDLE_INTERVAL: 数值, 两次 GAE 请求的间隔时间, 以秒为单位.
        GAEServer.REQUEST_HEADERS: 字典, 向 GAE 请求时所发送的 HEADER
        GAEServer.TRANS_MAXRETRY: 整数, GAE 数据交换失败后的重试次数
                    
EndPoint: 表示一个终点(请求被直接处理了):
    常用的: EndPoint(返回状态码, 返回的解释*, 返回的头信息*, 返回的主体*, 别名*) 
    快捷: 返回一个元组与此等效, 如: (404, "Not Found") 
    原型: EndPoint(code, msgs="", head="", body="", alias="Fast")
            extends Adapter implements Reusable
    @param [str/int] code 返回的HTTP状态码, 为三位数字或100-599的整数, 参 rfc2616.
    @param [str] msgs 返回的解释, 可包含 SPACE 和 TAB, 不可包含 CR 或 LF.
    @param [str/dict] head 返回的头信息, 字符串时以 LF 区分各条header. 字典时 key 大小写无关.
    @param [str] body 返回的主体, 程序自动计算 content-length.
    @param [str] alias 别名, 可作为ID, 如果设置了日志输出也会使用该值        
    **高级的: EndPoint(处理的回调函数, 附加参数...)
    原型: EndPoint([function]callback, [list]*args)
            extends DirectProxy implements Reusable
    回调函数原型: callback([Handler]request, [list]*args)
    注意: 以下 Header 由程序自动处理:
        Connection, Proxy-Connection, Content-Length, Server, Date
    隐藏参数:
        EndPoint.KEEP_ALIVE: 正整数, 持久连接时发送的 Keep-Alive 值, 为零不发送             
        
另外, 如果在FIND_ROUTE 中返回一个列表, 程序将随机选择一个列表内的元素(可用于负载分配).
任何不可识别的返回, 包含函数出错将使用 DEFAULT_ROUTE(这也是为啥它不允许用快捷).

GAEServer, LocalHost, DirectProxy, EndPoint 
都是可重用的, 相同的参数将创建出一模一样的实例(引用相等/A is B)
对比之下, 返回特定实例 优于 返回特定类型 优于 快捷式返回(代价极低, 可以忽略差距)
并且有两个共同的隐藏参数
    
    参数: FAIL_SAFE
    相关: 仅当 RETRIES_ROUTE 参数设置为大于0 的值才起作用
    取值: Adapter 实例, 或 None(不重试)
    作用: 本类型(实例)的路由失败后, 
          如果条件允许则用 FAIL_SAFE 指定的路由 继续尝试.
          当设置为 None 则不重试(为了尽快地报错).
    举例: LocalHost.FAIL_SAFE= GAEServer(...)
          该配置要求 本地直连失败后使用 GAE代理 重试.
    
    **参数: RETRY_CODES
    相关: 仅当 FAIL_SAFE 为有效的 Adapter 实例才起作用
    取值: 一个元组(tuple), 包含一系列整数, 参 WSA 系列 API 的说明
    作用: 当出现的 socket.error的errno 为其中的值时, 
        程序向 对应的FailSafe 发出 重试(Retry) 请求
        关于 errno 的资料可以 参照 python 安装目录 下的 errno.py
    举例: RETRY_CODES= (10054,) 出现 "连接重置" 时 发出重试(默认)    
    

Matcher: 比较器, 使用类似 abp 的规则, 带有优化和缓存处理
    语法: Matcher(别名*, 存储文件*, 优化参数*, 缓存控制*, 查询串可匹配*, 大小写敏感*)
    原型: Matcher(alias="matcher", file=None, hashkey=None,
         usecache=True, query=False, case=False)
    隐藏参数: (请不要随意改动这些参数尤其是 FE,FR 除非你知道在干啥.)
        Matcher.CACHE_LIMITG: 一对整数, 为第一代CY, 第二代CO缓存最大条目
            CY 决定缓存捕获能力, CO决定缓存刷新频率.
        **Matcher.CACHE_FBACK: 一对数值(tuple), 为锁相反馈参数FR, 锁相反馈参数FE 
            FR/FE 决定缓存的更新比例. FR*FE 决定缓存的跟踪速度. 
        Matcher.HASH_OPTIMIZE: 默认的优化参数, 正则表达式, 决定 hashkey 的生成
        Matcher.HASH_BADKEY: 最低优先级的 KEY, 防止生成过于沉重的 hashkey
        Matcher.PATH_RULE: 函数, 如何将一个请求地址转化一条规则(默认选择主域名和根目录)
    初始化较慢(需要分析规则以优化, 正则规则同样能被优化)
    运行时支持(内存许可的)的任意条目面不改色(在cache的跟踪建立后, 时间消耗可以忽略不计了)
    init 方法是规则批量加入, 通常是初始化用
    update 方法是规则单个加入/删除, 通常是运行时用
    state 方法是输出该比较器的状态以供参考             
       
Logger: 日志输出流, 就是 主程序的 logger. 
    语法: Logger.等级(输出字符串, 格式化参数*, 附加选项=取值*)
    说明: 用来向日志流输出东西(print 无法输出到日志流上)
    举例: Logger.debug("%s ...", 1+2) 输出一条 debug 级别的日志
    
**Static: 静态区域, 一个上下文管理器, 在其上下文内执行的语句被认为是一次性的
    语法: with Static(上下文标识*): ...
    说明: 在此区域下的配置语句不受文件重载的影响, 它们能且仅能执行一次.
    举例: with Static(): print "hello"; 
        # 不论配置文件重载多少次, 不论 with Static() 出现多少次(比如位于循环内), 
        # "hello" 仅被输出一次. 这点和 if FirstRun: 不一样.                
    附加: 不同 contextID 的区域互不相干, 如同原有的书写
    高级说明: (这个需要有编程经验,并需要具备一定的动态语言知识)    
        标准语法: with Static(contextID): <suite>
        其原理是具有相同的 contextID 的块在第二次执行的时候(
            不论是因重载再执行还是循环执行还是写了相同的语句)
        其区域内的命名空间作用域(namespace scope)被设置为空
        从而导致任何名称访问(包括语言的内置函数)完全失效
        
** 带有此标识的项目通常需要具备相关知识才能完全理解其原理, 
    一般用户照猫画虎即可, 不必深究.
"""
     